package org.nicocube.airain.domain.server.data;

import java.lang.reflect.Field;
import java.util.List;

import org.nicocube.airain.domain.client.data.OrderedStorable;
import org.nicocube.airain.domain.client.data.StorageException;
import org.nicocube.airain.domain.client.utils.DomainException;
import org.nicocube.airain.domain.client.utils.SequenceHolder;
import org.nicocube.airain.domain.server.criteria.ConstraintCriteria;
import org.nicocube.airain.domain.server.data.ContainerProvider.CancelEventException;
import org.nicocube.airain.domain.server.data.ContainerProvider.EventHandler;
import org.nicocube.airain.domain.server.data.ContainerProvider.EventType;
import org.nicocube.airain.domain.server.utils.SequenceHolderImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.db4o.ObjectContainer;

/**
 * Utility class to manage <tt>OrderedStorage</tt> of a type of <tt>OrderedStorable</tt> data
 * 
 * @author nicolas
 * @version 0.1
 *
 * @param <T>
 */
public class OrderedStorage<T extends OrderedStorable> extends Storage<T> {

	private static final Logger log = LoggerFactory
	.getLogger(OrderedStorage.class);

	private SequenceHolder seq;

	protected OrderedStorage(final Class<T> clazz, ContainerProvider db) {
		super(clazz, db);

		final ObjectContainer container = getContainer();

		try {
			db.registerEvent(clazz, EventType.creating, new EventHandler() {
				@Override
				public void handle(Object obj) throws CancelEventException {
					T t = clazz.cast(obj);
					if (t.isSaved()) {
						if (log.isDebugEnabled())
							log.debug("reconciliation:" + t.toString());
						// don't insert because T is already in the
						// container
						T existing = retrieveByOrder(t.getOrder());
						if (existing != null) {
							if (log.isDebugEnabled())
								log.debug("existing:"
										+ existing.toString());
							long l = container.ext().getID(existing);
							container.ext().bind(t, l);
							container.store(t);

							// FIXME was : cancellableArgs.cancel();
							throw new CancelEventException();
						}
					} else {
						try {
							t.setOrder(getSequenceHolder());
							if (log.isDebugEnabled())
								log.debug("creating new object:"
										+ t.toString());
						} catch (DomainException e) {
							throw new IllegalStateException(
									"Problem while setting Order to new object",
									e);
						} catch (StorageException e) {
							throw new IllegalStateException(
									"Problem while setting Order to new object",
									e);
						}
					}
				}
			});
		} catch (StorageException e) {
			throw new IllegalStateException(
					"Problem while adding handler for type "+clazz,
					e);
		}

		try {
			db.registerEvent(clazz, EventType.updated, new EventHandler() {
				@Override
				public void handle(Object obj) throws CancelEventException {
					T t = clazz.cast(obj);
					if (log.isDebugEnabled())
						log.debug("storing dependencies");

					@SuppressWarnings("unchecked")
					Class<T> clazz = (Class<T>) t.getClass();

					for (Field f : clazz.getDeclaredFields()) {
						f.setAccessible(true);
						Object o;
						try {
							o = f.get(t);
						} catch (IllegalArgumentException e) {
							throw new IllegalStateException(
									"Problem while saving dependencies : "
									+ f.getName(), e);
						} catch (IllegalAccessException e) {
							throw new IllegalStateException(
									"Problem while saving dependencies : "
									+ f.getName(), e);
						}
						container.store(o);
					}
				}
			});
		} catch (StorageException e) {
			throw new IllegalStateException(
					"Problem while adding handler for type "+clazz,
					e);
		}
	}

	/**
	 * Retrieve an {@link OrderedStorable} by his order
	 * @param order
	 * @return
	 */
	public T retrieveByOrder(long order) {
		return retrieveOne(new ConstraintCriteria<Long>("order", order));
	}

	/**
	 * Retrieve the {@link SequenceHolder} for this {@link OrderedStorage}
	 * @return
	 * @throws StorageException
	 */
	protected SequenceHolder getSequenceHolder() throws StorageException {
		if (seq == null)
			importSequence();
		return seq;
	}

	/**
	 * Synchronized method to ensure single loading of the {@link SequenceHolder}
	 * @throws StorageException
	 */
	private synchronized void importSequence() throws StorageException {
		if (seq == null) {
			Storage<SequenceHolderImpl> sto = new Storage<SequenceHolderImpl>(
					SequenceHolderImpl.class, containerProvider);
			List<SequenceHolderImpl> l = sto
			.retrieve(new ConstraintCriteria<String>("className", clazz
					.getName()));

			if (l != null && !l.isEmpty()) {
				seq = l.get(0).setStorage(sto);
			} else {
				seq = new SequenceHolderImpl(clazz.getName(),new Storage<SequenceHolderImpl>(SequenceHolderImpl.class,containerProvider));
			}
		}
	}

}
